//
//  Cartridge.h
//  Clock Signal
//
//  Created by Thomas Harte on 17/03/2017.
//  Copyright 2017 Thomas Harte. All rights reserved.
//

#pragma once

#include "Processors/6502/6502.hpp"
#include "Machines/Atari/2600/Bus.hpp"

namespace Atari2600::Cartridge {

class BusExtender: public CPU::MOS6502::BusHandler {
public:
	BusExtender(const uint8_t *const rom_base, const std::size_t rom_size) :
		rom_base_(rom_base), rom_size_(rom_size) {}

	void advance_cycles(int) {}

protected:
	const uint8_t *rom_base_;
	std::size_t rom_size_;
};

template<class T> class Cartridge:
	public CPU::MOS6502::BusHandler,
	public Bus {

public:
	Cartridge(const std::vector<uint8_t> &rom) :
		m6502_(*this),
		rom_(rom),
		bus_extender_(rom_.data(), rom.size()) {
		// The above works because bus_extender_ is declared after rom_ in the instance storage list;
		// consider doing something less fragile.
	}

	void run_for(const Cycles cycles) override {
		// Horizontal counter resets are used as a proxy for whether this really is an Atari 2600
		// title. Random memory accesses are likely to trigger random counter resets.
		horizontal_counter_resets_ = 0;
		cycle_count_ = cycles;
		m6502_.run_for(cycles);
	}

	/*!
		Adjusts @c confidence_counter according to the results of the most recent run_for.
	*/
	void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) override {
		if(cycle_count_.as_integral() < 200) return;
		if(horizontal_counter_resets_ > 10)
			confidence_counter.add_miss();
	}

	void set_reset_line(const bool state) override	{ m6502_.set_reset_line(state);	}

	// to satisfy CPU::MOS6502::Processor
	Cycles perform_bus_operation(
		const CPU::MOS6502::BusOperation operation,
		const uint16_t address,
		uint8_t *const value
	) {
		uint8_t returnValue = 0xff;
		int cycles_run_for = 3;

		// this occurs as a feedback loop: the 2600 requests ready, then performs the cycles_run_for
		// leap to the end of ready only once ready is signalled because on a 6502 ready doesn't take
		// effect until the next read; therefore it isn't safe to assume that signalling ready immediately
		// skips to the end of the line.
		if(operation == CPU::MOS6502::BusOperation::Ready)
			cycles_run_for = tia_.get_cycles_until_horizontal_blank(cycles_since_video_update_);

		cycles_since_speaker_update_ += Cycles(cycles_run_for);
		cycles_since_video_update_ += Cycles(cycles_run_for);
		cycles_since_6532_update_ += Cycles(cycles_run_for / 3);
		bus_extender_.advance_cycles(cycles_run_for / 3);

		if(is_access(operation)) {
			// give the cartridge a chance to respond to the bus access
			bus_extender_.perform_bus_operation(operation, address, value);

			// check for a RIOT RAM access
			if((address&0x1280) == 0x80) {
				if(is_read(operation)) {
					returnValue &= mos6532_.get_ram(address);
				} else {
					mos6532_.set_ram(address, *value);
				}
			}

			// check for a TIA access
			if(!(address&0x1080)) {
				if(is_read(operation)) {
					const uint16_t decodedAddress = address & 0xf;
					switch(decodedAddress) {
						case 0x00:		// missile 0 / player collisions
						case 0x01:		// missile 1 / player collisions
						case 0x02:		// player 0 / playfield / ball collisions
						case 0x03:		// player 1 / playfield / ball collisions
						case 0x04:		// missile 0 / playfield / ball collisions
						case 0x05:		// missile 1 / playfield / ball collisions
						case 0x06:		// ball / playfield collisions
						case 0x07:		// player / player, missile / missile collisions
							returnValue &= tia_.get_collision_flags(decodedAddress);
						break;

						case 0x08:
						case 0x09:
						case 0x0a:
						case 0x0b:
							// TODO: pot ports
							returnValue &= 0;
						break;

						case 0x0c:
						case 0x0d:
							returnValue &= tia_input_value_[decodedAddress - 0x0c];
						break;
					}
				} else {
					const uint16_t decodedAddress = address & 0x3f;
					switch(decodedAddress) {
						case 0x00:	update_video(); tia_.set_sync(*value & 0x02);		break;
						case 0x01:	update_video();	tia_.set_blank(*value & 0x02);		break;

						case 0x02:	m6502_.set_ready_line(true);						break;
						case 0x03:
							update_video();
							tia_.reset_horizontal_counter();
							horizontal_counter_resets_++;
						break;
							// TODO: audio will now be out of synchronisation. Fix.

						case 0x04:
						case 0x05:	update_video();	tia_.set_player_number_and_size(decodedAddress - 0x04, *value);	break;
						case 0x06:
						case 0x07:	update_video();	tia_.set_player_missile_colour(decodedAddress - 0x06, *value);		break;
						case 0x08:	update_video();	tia_.set_playfield_ball_colour(*value);								break;
						case 0x09:	update_video();	tia_.set_background_colour(*value);									break;
						case 0x0a:	update_video();	tia_.set_playfield_control_and_ball_size(*value);					break;
						case 0x0b:
						case 0x0c:	update_video();	tia_.set_player_reflected(decodedAddress - 0x0b, !((*value)&8));	break;
						case 0x0d:
						case 0x0e:
						case 0x0f:	update_video();	tia_.set_playfield(decodedAddress - 0x0d, *value);					break;
						case 0x10:
						case 0x11:	update_video(); tia_.set_player_position(decodedAddress - 0x10);					break;
						case 0x12:
						case 0x13:	update_video(); tia_.set_missile_position(decodedAddress - 0x12);					break;
						case 0x14:	update_video();	tia_.set_ball_position();											break;
						case 0x1b:
						case 0x1c:	update_video(); tia_.set_player_graphic(decodedAddress - 0x1b, *value);				break;
						case 0x1d:
						case 0x1e:	update_video(); tia_.set_missile_enable(decodedAddress - 0x1d, (*value)&2);			break;
						case 0x1f:	update_video(); tia_.set_ball_enable((*value)&2);									break;
						case 0x20:
						case 0x21:	update_video(); tia_.set_player_motion(decodedAddress - 0x20, *value);				break;
						case 0x22:
						case 0x23:	update_video(); tia_.set_missile_motion(decodedAddress - 0x22, *value);				break;
						case 0x24:	update_video(); tia_.set_ball_motion(*value);										break;
						case 0x25:
						case 0x26:	tia_.set_player_delay(decodedAddress - 0x25, (*value)&1);							break;
						case 0x27:	tia_.set_ball_delay((*value)&1);													break;
						case 0x28:
						case 0x29:	update_video(); tia_.set_missile_position_to_player(decodedAddress - 0x28, (*value)&2);		break;
						case 0x2a:	update_video(); tia_.move();														break;
						case 0x2b:	update_video(); tia_.clear_motion();												break;
						case 0x2c:	update_video(); tia_.clear_collision_flags();										break;

						case 0x15:
						case 0x16:	update_audio(); tia_sound_.set_control(decodedAddress - 0x15, *value);				break;
						case 0x17:
						case 0x18:	update_audio(); tia_sound_.set_divider(decodedAddress - 0x17, *value);				break;
						case 0x19:
						case 0x1a:	update_audio(); tia_sound_.set_volume(decodedAddress - 0x19, *value);				break;
					}
				}
			}

			// check for a PIA access
			if((address&0x1280) == 0x280) {
				update_6532();
				if(is_read(operation)) {
					returnValue &= mos6532_.read(address);
				} else {
					mos6532_.write(address, *value);
				}
			}

			if(is_read(operation)) {
				*value &= returnValue;
			}
		}

		if(!tia_.get_cycles_until_horizontal_blank(cycles_since_video_update_)) m6502_.set_ready_line(false);

		return Cycles(cycles_run_for / 3);
	}

	void flush() override {
		update_audio();
		update_video();
		audio_queue_.perform();
	}

protected:
	CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, Cartridge<T>, true> m6502_;
	std::vector<uint8_t> rom_;

private:
	T bus_extender_;
	int horizontal_counter_resets_ = 0;
	Cycles cycle_count_;
};

}
